/*
 * <one line to give the program's name and a brief idea of what it does.>
 * Copyright (C) 2019
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef DBSQLITE_H
#define DBSQLITE_H

#include "db.hpp"

#include <mutex>
#include <string>
#include <vector>
#include <map>
#include <memory>

enum QUERY : unsigned  {
	BUILDS_CREATE,
	BUILDS_ERASE,
	BUILDS_SIZE,
	BUILDS_ADDS,
	BUILDS_UPDATE
};

class DBLMDB;
struct sqlite3;
struct sqlite3_stmt;

namespace spdlog {
	class logger;
}

class SQLStatement {
public:
	/**
	 * \param db pointer to sqlite3* database
	 */
	SQLStatement(sqlite3 * db, const std::string & query);
	SQLStatement(const SQLStatement & other) = delete;
	~SQLStatement();

	/**
	 * \brief prepare statement with sql query
	 */
	bool prepare(const std::string & query = std::string());

	/**
	 * \brief execute statement
	 * \return SQLITE_* error code
	 */
	int run();

	/**
	 * \brief reset statement
	 * return true if all ok, false if we get some error
	 */
	bool reset();

	/**
	 * \brief if statement has parameters they must be binded before execution
	 * \param index number of parameter
	 * \param value parameter value
	 */
	bool bind(const uint32_t & index, const uint32_t & value);

	/**
	 * \brief binded params must be cleaned after statement executing
	 * setting all parameters to null
	 */
	void clear();

	sqlite3_stmt * raw() {
		return m_Statement;
	}

private:
	sqlite3 * m_Database = nullptr;
	sqlite3_stmt * m_Statement = nullptr;
	std::string m_SQL;
	std::shared_ptr<spdlog::logger> m_Log;
};

class DBSQLite : public DataBase {
public:
	DBSQLite(const std::string & dbname, bool rewrite = true);
	DBSQLite(const DBSQLite & other) = delete;
	~DBSQLite();

	bool empty() override;
	void erase() override;
	bool get(void * data, const std::size_t & size) override;
	void put(void * data, const std::size_t & size) override;
	std::size_t size() override;

	bool updateBuild(PathData * data) override;
	std::size_t addBuilds(std::vector< PathData > * data, PathData * build, DataBase * queue) override;

	/**
	 * find path from end to begin
	 * \param[out] db database for founded results
	 */
	void findPathCommon(DataBase * db);

	/**
	 * megre tables
	 * \param[in] dbname database name to merge
	 */
	std::size_t copyDataTo(const std::string & dbname);

	/**
	 * move unused data to other sqliteDB
	 */
	std::size_t moveUnuseDataTo(const std::string & dbname);

	/**
	 * move data from lmdb
	 */
	std::size_t moveFromLMDB(DBLMDB *db);

	/**
	 * delete from database all entries where min = max
	 * it means we just upgrade part, no need this data
	 */
	std::size_t removeZeroWins();
	std::size_t removeZeroWinsPartial();

	/**
	 * create database and recreate tables
	 */
	static void prepareDB(const std::string & dbname);

private:
	/**
	 * Prepare statements for future usings
	 * Create tables in needed
	 * \param[in] rewrite if true tables will be recreate
	 */
	void init(bool rewrite);

	/**
	 * add custom build to database
	 * \param[in] data build data
	 * \return true if added, false if not
	 */
	bool addBuild(PathData * data);

	/**
	 * update build in database
	 * \param[in] data build data
	 * \return true if updated, false if not
	 */
	bool updateBuildSQLite(PathData * data);

	/**
	 * apply pragma to database
	 * \param[in] dbname database name
	 */
	void pragma(const std::string & dbname);

	/**
	 * Recursive path searching
	 * \param[in] id current row ID in database
	 * \param[in] max current max AI level
	 * \param[in] stop lowest AI level when we stop search
	 * \param[out] path vector for founded row ids
	 * \param[in,out] stmt sqlite prepared statement
	 * \return true if we reach stop point, false if not
	 */
	bool findPathRecursive(const uint32_t id, const uint16_t max, const uint16_t stop, std::vector<uint32_t> * path, SQLStatement * stmt);

	std::shared_ptr<spdlog::logger> m_Log;
	sqlite3 * m_Database = nullptr;
	std::map<QUERY, SQLStatement *> m_Statements;
	std::mutex m_Mutex;
	const std::string m_DBName;
};

#endif // DBSQLITE_H
